package com.litongjava.db.activerecord.generator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.jfinal.kit.JavaKeyword;
import com.jfinal.kit.Kv;
import com.jfinal.kit.StrKit;
import com.jfinal.template.Engine;

/**
 * Base model 生成器
 */
public class BaseModelGenerator {

  protected Engine engine;
  protected String template = "/com/litongjava/db/activerecord/generator/base_model_template.jf";

  protected String baseModelPackageName;
  protected String baseModelOutputDir;
  protected boolean generateChainSetter = false;

  protected JavaKeyword javaKeyword = JavaKeyword.me;

  /**
   * 针对 Model 中七种可以自动转换类型的 getter 方法，调用其具有确定类型返回值的 getter 方法
   * 享用自动类型转换的便利性，例如 getInt(String)、getStr(String)
   * 其它方法使用泛型返回值方法： get(String)
   * 注意：jfinal 3.2 及以上版本 Model 中的六种 getter 方法才具有类型转换功能
   */
  @SuppressWarnings("serial")
  protected Map<String, String> getterTypeMap = new HashMap<String, String>() {
    {
      put("java.lang.String", "getStr");
      put("java.lang.Integer", "getInt");
      put("java.lang.Long", "getLong");
      put("java.lang.Double", "getDouble");
      put("java.lang.Float", "getFloat");
      put("java.lang.Short", "getShort");
      put("java.lang.Byte", "getByte");

      // 新增两种可自动转换类型的 getter 方法
      put("java.util.Date", "getDate");
      put("java.time.LocalDateTime", "getLocalDateTime");

      // 新增 TypeKit 转换类之后，支持了更多的类型
      put("java.lang.Boolean", "getBoolean");
      put("java.math.BigDecimal", "getBigDecimal");
      put("java.math.BigInteger", "getBigInteger");
    }
  };

  public BaseModelGenerator(String baseModelPackageName, String baseModelOutputDir) {
    if (StrKit.isBlank(baseModelPackageName)) {
      throw new IllegalArgumentException("baseModelPackageName can not be blank.");
    }
    if (baseModelPackageName.contains("/") || baseModelPackageName.contains("\\")) {
      throw new IllegalArgumentException("baseModelPackageName error : " + baseModelPackageName);
    }
    if (StrKit.isBlank(baseModelOutputDir)) {
      throw new IllegalArgumentException("baseModelOutputDir can not be blank.");
    }

    this.baseModelPackageName = baseModelPackageName;
    this.baseModelOutputDir = baseModelOutputDir;

    initEngine();
  }

  protected void initEngine() {
    engine = new Engine();
    engine.setToClassPathSourceFactory(); // 从 class path 内读模板文件
    engine.addSharedMethod(new StrKit());
    engine.addSharedObject("getterTypeMap", getterTypeMap);
    engine.addSharedObject("javaKeyword", javaKeyword);
  }

  /**
   * 使用自定义模板生成 base model
   */
  public void setTemplate(String template) {
    this.template = template;
  }

  public void setGenerateChainSetter(boolean generateChainSetter) {
    this.generateChainSetter = generateChainSetter;
  }

  public void generate(List<TableMeta> tableMetas) {
    System.out.println("Generate base model ...");
    System.out.println("Base Model Output Dir: " + baseModelOutputDir);

    for (TableMeta tableMeta : tableMetas) {
      genBaseModelContent(tableMeta);
    }
    writeToFile(tableMetas);
  }

  protected void genBaseModelContent(TableMeta tableMeta) {
    Kv data = Kv.by("baseModelPackageName", baseModelPackageName);
    data.set("generateChainSetter", generateChainSetter);
    data.set("tableMeta", tableMeta);

    tableMeta.baseModelContent = engine.getTemplate(template).renderToString(data);
  }

  protected void writeToFile(List<TableMeta> tableMetas) {
    try {
      for (TableMeta tableMeta : tableMetas) {
        writeToFile(tableMeta);
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * base model 覆盖写入
   */
  protected void writeToFile(TableMeta tableMeta) throws IOException {
    File dir = new File(baseModelOutputDir);
    if (!dir.exists()) {
      dir.mkdirs();
    }

    String target = baseModelOutputDir + File.separator + tableMeta.baseModelName + ".java";
    try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(target), "UTF-8")) {
      osw.write(tableMeta.baseModelContent);
    }
  }

  public String getBaseModelPackageName() {
    return baseModelPackageName;
  }

  public String getBaseModelOutputDir() {
    return baseModelOutputDir;
  }
}
